/******************************************************
*  FileName :bsp_joystick.c
*  Author : duyanning
*  Description : JOY手柄驱动
*  Data : 2020-03-06
******************************************************/

/*********************************************************************
 * INCLUDES
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <fcntl.h> 
#include <unistd.h> 
#include <errno.h> 

#include <linux/joystick.h> 
#include <sys/types.h> 
#include <sys/time.h> 
#include <sys/stat.h> 

#include "bsp_joystick.h"

/*********************************************************************
 *  EXTERNAL VARIABLES
 */

/*********************************************************************
 * MACROS
 */

#define JOY_NAME_MAX_LEN    32
#define LOG_ERR printf
#define LOG_INF printf
#define LOG_WRN printf

/*********************************************************************
 * TYPES
 */

typedef struct _joy_context
{
    pthread_t iThreadId;
    pthread_attr_t stThreadAttr;
    int iState;
    int iJoyFd;
    char acJoyName[JOY_NAME_MAX_LEN];
    joy_callback_t pfJoyCallback;
} JOY_CONTEXT_S;

/*********************************************************************
 * FUNCTIONS DECLARE
 */

void *JOY_Routine(void *pArg);

static int JOY_StateConnect(void);
static int JOY_StateReady(void);
static int JOY_StateRun(void);
static int JOY_StateRecovery(void);
static int JOY_StateStop(void);
static int JOY_StateError(void);

/*********************************************************************
 *  VARIABLES
 */

static JOY_CONTEXT_S s_stJoyContext = { 0 };

/*********************************************************************
 * FUNCTIONS
 */

/**
*   @Description: 与手柄建立连接
*   @param: pcJoyName - 手柄设备名称
*   @return ：错误信息
*/
int JOY_Start(char *pcJoyName)
{
    int iRet;
    
    if (JOY_IDLE != s_stJoyContext.iState)
    {
        return JOY_ERR;
    }

    if (JOY_NAME_MAX_LEN <= strlen(pcJoyName))
    {
        return JOY_ERR;
    }

    strcpy(s_stJoyContext.acJoyName, pcJoyName);
    s_stJoyContext.iJoyFd = -1;
    s_stJoyContext.iState = JOY_CONNECT;

    pthread_attr_init(&s_stJoyContext.stThreadAttr);
    pthread_attr_setscope(&s_stJoyContext.stThreadAttr, PTHREAD_SCOPE_SYSTEM);
    iRet = pthread_create(&s_stJoyContext.iThreadId, &s_stJoyContext.stThreadAttr, JOY_Routine, &s_stJoyContext);
    if (iRet < 0)
    {
        LOG_ERR("create the joy thread failure:%s\n", strerror(errno));
        return JOY_ERR;
    }
    
    return JOY_OK;
}
 
/**
*   @Description: 与手柄断开连接
*   @param: NA
*   @return ：错误信息
*/
int JOY_Stop(void)
{
    s_stJoyContext.iState = JOY_STOP;
    return JOY_OK;
}

/**
*   @Description: 注册事件回调函数
*   @param: pfJoyCallback - 回调函数指针
*   @return ：错误信息
*/
int JOY_RegisterCallback(joy_callback_t pfJoyCallback)
{
    if (s_stJoyContext.pfJoyCallback)
    {
        LOG_WRN("joy already register callback.\n");
        return JOY_ERR;
    }

    s_stJoyContext.pfJoyCallback = pfJoyCallback;
    return JOY_OK;
}

// 运行主函数
void *JOY_Routine(void *pArg)
{
    while (1)
    {
        switch (s_stJoyContext.iState)
        {
            case JOY_IDLE:
            {
                sleep(1);
                break;
            }
            case JOY_CONNECT:
            {
                if (JOY_OK != JOY_StateConnect())
                {
                    usleep(500000); // 500ms
                }
                break;
            }
            case JOY_READY:
            {
                JOY_StateReady();
                break;
            }
            case JOY_RUN:
            {
                JOY_StateRun();
                break;
            }
            case JOY_RECOVERY:
            {
                JOY_StateRecovery();
                break;
            }
            case JOY_STOP:
            {
                JOY_StateStop();
                break;
            }
            case JOY_ERROR:
            {
                JOY_StateError();
                goto EXIT;
            }
            default:
            {
                sleep(1);
                break;
            }
        }
    }

EXIT:
    return NULL;
}

static int JOY_Open(void)
{
    int iJoyFd;
    
    iJoyFd = open(s_stJoyContext.acJoyName, O_RDONLY);
    if (iJoyFd != -1)
    {
      // There seems to be a bug in the driver or something where the
      // initial events that are to define the initial state of the
      // joystick are not the values of the joystick when it was opened
      // but rather the values of the joystick when it was last closed.
      // Opening then closing and opening again is a hack to get more
      // accurate initial state data.
      LOG_WRN("joy first open fail:%s\n", strerror(errno));
      
      close(iJoyFd);
      iJoyFd = open(s_stJoyContext.acJoyName, O_RDONLY);
    }
    
    if (0 <= iJoyFd)
    {
        int rc;
        int number_of_axes;
        int number_of_btns;
        char js_name_str[128];

        s_stJoyContext.iJoyFd = iJoyFd;
        LOG_INF("joy open sucessful.\n");

        rc = ioctl(iJoyFd, JSIOCGAXES, &number_of_axes);
        if (rc != -1)
        {
            LOG_INF("number_of_axes:%d\n", number_of_axes);
        }

        rc = ioctl(iJoyFd, JSIOCGBUTTONS, &number_of_btns);
        if (rc != -1) 
        {
            LOG_INF("number_of_btns:%d\n", number_of_btns);
        }

        if (ioctl(iJoyFd, JSIOCGNAME(sizeof(js_name_str)), js_name_str) < 0)
        {
            LOG_INF(js_name_str, "Unknown", sizeof(js_name_str));
        }
  
        LOG_INF("joystick Name: %s\n", js_name_str);
        return JOY_OK;
    }
    else
    {
        LOG_ERR("joy open fail:%s\n", strerror(errno));
        return JOY_ERR;
    }
}

static int JOY_Close(void)
{
    if (s_stJoyContext.iJoyFd != -1)
    {
        close(s_stJoyContext.iJoyFd);
    }

    LOG_INF("joy close.\n");
    return JOY_OK;
}

static int JOY_ReadEvent(struct js_event *pstJsEvt)
{
    int iJoyFd;
    int iRet;
    fd_set stReadSet;
    struct timeval timeout = {0, 0};

    if (NULL == pstJsEvt)

    {
        LOG_ERR("joy read, param invalid.\n");
        return JOY_ERR;
    }

    if (0 > s_stJoyContext.iJoyFd)
    {
        LOG_ERR("joy read event, joy fd invalid.\n");
        return JOY_ERR;
    }

    iJoyFd = s_stJoyContext.iJoyFd;
    FD_ZERO(&stReadSet);
    FD_SET(iJoyFd, &stReadSet);

    iRet = select(iJoyFd+1, &stReadSet, NULL, NULL, &timeout);
    if (iRet > 0 && FD_ISSET(iJoyFd, &stReadSet))
    {
        if (read(iJoyFd, pstJsEvt, sizeof(struct js_event)) == -1 && errno != EAGAIN)
        {
            // Joystick is probably closed. Definitely occurs.
            LOG_INF("joy read event fail, switch to recovery state.\n");
            s_stJoyContext.iState = JOY_RECOVERY;
            return JOY_ERR;
        }
        
        LOG_INF("joy event, type:0x%08x, value:%d, number:%d.\n", pstJsEvt->type, pstJsEvt->value, pstJsEvt->number);
        return JOY_OK;
    }
    else
    {
        return JOY_ERR;
    }
}

static int JOY_HandleEvent(struct js_event *pstJsEvt)
{
    JOY_EVENT_S stJoyEvent = { 0 };
    
    if (NULL == pstJsEvt)
    {
        LOG_ERR("joy handle, param invalid.\n");
        return JOY_ERR;
    }

    if (pstJsEvt->type & JS_EVENT_INIT)
    {
        LOG_INF("joy init event.\n");
        return JOY_OK;
    }

    if (!s_stJoyContext.pfJoyCallback)
    {
        return JOY_ERR;
    }

    if (pstJsEvt->type & JS_EVENT_BUTTON)
    {
        stJoyEvent.uiType |= JOY_EVT_BUTTON;
    }

    if (pstJsEvt->type & JS_EVENT_AXIS)
    {
        stJoyEvent.uiType |= JOY_EVT_AXIS;
    }

    if (stJoyEvent.uiType)
    {
        stJoyEvent.iNumber = pstJsEvt->number;
        stJoyEvent.iValue = pstJsEvt->value;
        s_stJoyContext.pfJoyCallback(&stJoyEvent);
    }
    
    return JOY_OK;
}

static int JOY_StateConnect(void)
{
    if (JOY_OK == JOY_Open())
    {
        s_stJoyContext.iState = JOY_RUN;

        if (s_stJoyContext.pfJoyCallback)
        {
            JOY_EVENT_S stJoyEvent = { 0 };
            stJoyEvent.uiType = JOY_EVT_CONN;
            s_stJoyContext.pfJoyCallback(&stJoyEvent);
        }
        return JOY_OK;
    }
    
    return JOY_ERR;
}

static int JOY_StateReady(void)
{
    return JOY_OK;
}

static int JOY_StateRun(void)
{
    struct js_event stJsEvent;
    
    if (JOY_OK != JOY_ReadEvent(&stJsEvent))
    {
        return JOY_ERR;
    }

    JOY_HandleEvent(&stJsEvent);
    return JOY_OK;
}

static int JOY_StateRecovery(void)
{
    if (s_stJoyContext.iJoyFd != -1)
    {
        close(s_stJoyContext.iJoyFd);
    }

    s_stJoyContext.iJoyFd = -1;
    s_stJoyContext.iState = JOY_CONNECT;
    
    if (s_stJoyContext.pfJoyCallback)
    {
        JOY_EVENT_S stJoyEvent = { 0 };

        LOG_INF("joy state switch to recovery.\n");
        stJoyEvent.uiType = JOY_EVT_DISCONN;
        s_stJoyContext.pfJoyCallback(&stJoyEvent);
    }

    return JOY_OK;
}

static int JOY_StateStop(void)
{
    JOY_Close();

    s_stJoyContext.iJoyFd = -1;
    s_stJoyContext.iState = JOY_IDLE;

    return JOY_OK;
}

static int JOY_StateError(void)
{
    LOG_ERR("joy error.\n");
    return JOY_OK;
}

